﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;

using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.Domain;
using Fuse8.DomainFramework.CacheService;
using Fuse8.DomainFramework.Common.Exceptions;

namespace Fuse8.DomainFramework.DataAccess.SqlDatabase
{
    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    /// <typeparam name="TKey"></typeparam>
    public class SqlEntityDataRepository<TEntity, TKey> : IEntityDataRepository<TEntity, TKey>
        where TEntity : class, IEntityObject<TKey>, new()
        where TKey : IComparable<TKey>, IEquatable<TKey>
    {
        #region Fields

        private static readonly string _addSpNameFormat = "usp_{0}Add";
        private static readonly string _updateSpNameFormat = "usp_{0}Update";
        private static readonly string _removeSpNameFormat = "usp_{0}Delete";
        private static readonly string _findOneSpNameFormat = "usp_{0}SelectOne";
        private static readonly string _findAllSpNameFormat = "usp_{0}SelectAll";
        private static readonly string _typeName = typeof(TEntity).Name;
        
        private string _addSpName;
        private string _updateSpName;
        private string _removeSpName;
        private string _findOneSpName;
        private string _findAllSpName;
        private ISqlEntityDataMapper<TEntity, TKey> _mapper;
        private Func<string, object, object> _findParameters;

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        public virtual string AddSpName
        {
            get
            {
                return string.IsNullOrEmpty(_addSpName) ?
                    string.Format(_addSpNameFormat, _typeName) :
                    _addSpName;
            }
            set
            {
                _addSpName = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual string UpdateSpName
        {
            get
            {
                return string.IsNullOrEmpty(_updateSpName) ?
                    string.Format(_updateSpNameFormat, _typeName) :
                    _updateSpName;
            }
            set
            {
                _updateSpName = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual string RemoveSpName
        {
            get
            {
                return string.IsNullOrEmpty(_removeSpName) ?
                    string.Format(_removeSpNameFormat, _typeName) :
                    _removeSpName;
            }
            set
            {
                _removeSpName = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual string FindOneSpName
        {
            get
            {
                return string.IsNullOrEmpty(_findOneSpName) ?
                    string.Format(_findOneSpNameFormat, _typeName) :
                    _findOneSpName;
            }
            set
            {
                _findOneSpName = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual string FindAllSpName
        {
            get
            {
                return string.IsNullOrEmpty(_findAllSpName) ?
                    string.Format(_findAllSpNameFormat, _typeName) :
                    _findAllSpName;
            }
            set
            {
                _findAllSpName = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public Func<string, object, object> FindParameters
        {
            get 
			{
            	return _findParameters ?? (_findParameters = (spName, defaultParameters) => (!string.IsNullOrEmpty(spName) && spName.Equals(FindOneSpName, StringComparison.OrdinalIgnoreCase)) ? defaultParameters: null);
            }
        	set
            {
                _findParameters = value;
            }
        }

		private static IList<SqlParameter> BuildSqlParamteres (IEnumerable<SqlParameterInfo> parameterInfos, object parameter)
		{
			return parameterInfos == null ? new List<SqlParameter>() : parameterInfos.Select(p => new SqlParameter(p.Name, p.GetterDelegate(parameter) ?? DBNull.Value)).ToList();
		}

        /// <summary>
        /// 
        /// </summary>
        protected virtual ISqlEntityDataMapper<TEntity, TKey> Mapper
        {
            get
            {
                if (_mapper == null)
                {   
                    if (!DependencyServiceLocator.Current.IsDefaultServiceRegistered<ISqlEntityDataMapper<TEntity, TKey>>())
                    {
                        _mapper = SqlEntityDataRepositoryConfiguration<TEntity, TKey>.CreateMapper();
                        DependencyServiceLocator.Current.SetDefaultService<ISqlEntityDataMapper<TEntity, TKey>>(_mapper);
                    }
                    else
                    {
                        _mapper = DependencyServiceLocator.Current.GetInstance<ISqlEntityDataMapper<TEntity, TKey>>();
                    }
                }

                return _mapper;
            }
            set
            {
                _mapper = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual SqlUnitOfWork<TEntity> CurrentUnitOfWork
        {
            get
            {
                var unitOfWork = UnitOfWorkController<TEntity>.GetCurrentUnitOfWork() as SqlUnitOfWork<TEntity>;

                if (unitOfWork == null)
                {
                    throw new UnitOfWorkNotDefinedException();
                }

                return unitOfWork;
            }
        }

        #endregion

        #region IEntityDataRepository<TEntity,TKey> Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public void Add(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

            var unitOfWork = CurrentUnitOfWork;


            SqlUtility<TEntity, TKey>.ExecuteNonQueryWithMapping(unitOfWork.Connection, unitOfWork.Transaction,
                                                                            AddSpName, entityObject, Mapper, true);			
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public void Save(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

			var unitOfWork = CurrentUnitOfWork;

            SqlUtility<TEntity, TKey>.ExecuteNonQueryWithMapping(unitOfWork.Connection, unitOfWork.Transaction,
                                                                            UpdateSpName, entityObject, Mapper, false);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public void Remove(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");
        	Remove(entityObject.Id);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void Remove(TKey id)
        {
            var unitOfWork = CurrentUnitOfWork;
        	SqlUtility<TEntity, TKey>.ExecuteNonQuery(unitOfWork.Connection, unitOfWork.Transaction, RemoveSpName, new { Id = id }, Mapper);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public TEntity FindOne(TKey id)
        {
            var unitOfWork = CurrentUnitOfWork;

            var result = SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, FindOneSpName, FindParameters(FindOneSpName, new { Id = id }), Mapper);

            return result != null ?
                result.FirstOrDefault() :
                default(TEntity);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(Expression<Func<TEntity, bool>> expression)
        {
        	throw new NotSupportedException();
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public TEntity FindOne(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{		
			throw new NotImplementedException();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression)
        {
        	throw new NotSupportedException();
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{
    		throw new NotImplementedException();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntity> FindAll()
        {
            var unitOfWork = CurrentUnitOfWork;
            return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, FindAllSpName, FindParameters(FindAllSpName, null), Mapper)
                .AsQueryable();
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public IQueryable<TEntity> FindAll(Expression<Func<TEntity, object>> selectExpression)
    	{
			throw new NotImplementedException();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public TEntity FindOne(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            return command.ExecuteOne();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public IQueryable<TEntity> Find(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            return command.Execute();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        public void Execute(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            command.Execute();
        }

        /// <summary>
        /// 
        /// </summary>
        public DataRepositoryCacheOptions CacheOptions
        {
            get 
            {
                return DataRepositoryCacheOptions.FullCache;
            }
        }

        #endregion
    }
}